/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.cef.generator.coreGenerator.base.accessorgen.method;

import com.inspur.edp.caf.generator.baseInfo.ParameterInfo;
import com.inspur.edp.caf.generator.baseInfo.TypeInfo;
import com.inspur.edp.caf.generator.compileunit.CompileUnitGeneratorContext;
import com.inspur.edp.caf.generator.method.ClassMethodGenerator;
import com.inspur.edp.cef.core.data.DataUtils;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.gencommon.MediateType;
import com.inspur.edp.cef.generator.common.MediatePropertyInfo;
import com.inspur.edp.cef.generator.coreGenerator.base.accessorgen.AccessorClassGenContext;
import com.inspur.edp.cef.generator.coreGenerator.base.accessorgen.AccessorClassTypeInfo;
import com.inspur.edp.cef.generator.coreGenerator.base.accessorgen.property.NestedAccessorFieldGenerator;
import com.inspur.edp.cef.generator.coreGenerator.base.entitygen.property.DynamicPropSetPropertyGenerator;
import com.inspur.edp.cef.generator.overall.GeneratorUtil;
import com.inspur.edp.cef.generator.repository.RepositoryGenUtil;
import com.inspur.edp.cef.spi.common.UdtManagerUtil;
import com.inspur.edp.udt.designtime.api.entity.UnifiedDataTypeDef;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.StringLiteral;

public class AccessorInitializeNestedGen extends ClassMethodGenerator {

  protected final String nodeCode;
  private AccessorClassGenContext parentContext;
  private List<MediatePropertyInfo> dataType;
  private MediateType entityInterfaceType;
  private AccessorClassTypeInfo accessorClassTypeInfo;
  private boolean isReadonly;
  private static final String InnerDataMemName = "getInnerData";

  public AccessorInitializeNestedGen(AccessorClassGenContext context, String nodeCode,
      List<MediatePropertyInfo> dataType, MediateType entityInterfaceType,
      AccessorClassTypeInfo accessorClassTypeInfo, boolean isReadonly) {
    this.parentContext = context;
    this.nodeCode = nodeCode;
    this.dataType = dataType;
    this.entityInterfaceType = entityInterfaceType;
    this.accessorClassTypeInfo = accessorClassTypeInfo;
    this.isReadonly = isReadonly;
  }

  @Override
  protected Block buildMethodBody() {
    Block block = ast.newBlock();
    addPropertiesStatements(block);
    return block;
  }

  private void addPropertiesStatements(Block block) {
    for (MediatePropertyInfo info : dataType) {
      IGspCommonField field = info.getElement();
			if (field == null) {
				continue;
			}
			if (info.getExtendInfos().containsKey("AssoUdtAtrribute")) {
				continue;
			}

      if (field.getIsUdt()) {
				if (field.getObjectType() == GspElementObjectType.Association
						&& field.getChildAssociations().size() == 0) {
					continue;
				}
        addNestedPropertyStatement(ast, block, parentContext, isReadonly, nodeCode, field,
            parentContext.getEntityDataGenContext().getProjContext().getCefGenContext()
                .getCoreGenContext());
      } else if (field.getObjectType() == GspElementObjectType.DynamicProp) {
        getDynamicInitializer(block, field);
      }
    }
    addImport(UdtManagerUtil.class.getTypeName());
  }

  public static void addNestedPropertyStatement(AST ast, Block block,
      AccessorClassGenContext parentContext, boolean isReadonly, String nodeCode,
      IGspCommonField field, CompileUnitGeneratorContext compileUnitGenContext) {
    UnifiedDataTypeDef udtDef = ((UnifiedDataTypeDef) parentContext.getEntityDataGenContext()
        .getProjContext().getCefGenContext().getMetadataJitContext().getMetadata(field.getUdtID())
        .getContent());
    if (field.getObjectType() == GspElementObjectType.Association && field
        .containRefElementNotFromAssoUdt()) {
      addEnrichedAssoPropertyStatement(ast, block, parentContext, isReadonly, nodeCode, field,
          udtDef, compileUnitGenContext);
    } else {
      String configID = udtDef.getUdtType();
      Assignment assignment = ast.newAssignment();
      assignment.setLeftHandSide(
          ast.newSimpleName(DynamicPropSetPropertyGenerator.getFieldName(field)));
      assignment.setOperator(Assignment.Operator.ASSIGN);
      MethodInvocation createAccInvocation = RepositoryGenUtil.createMethodInvocation(
          ast,
          ast.newName(DataUtils.class.getName()), "createAccNested",
          Arrays.asList(ast.newThisExpression(),
              GeneratorUtil.getStringLiteral(ast, configID),
              GeneratorUtil.getStringLiteral(ast, field.getLabelID()),
              RepositoryGenUtil.createMethodInvocation(ast, null, "getIsReadonly", null)));
      assignment.setRightHandSide(createAccInvocation);
      block.statements().add(ast.newExpressionStatement(assignment));
    }
  }

  private static void addEnrichedAssoPropertyStatement(AST ast, Block block,
      AccessorClassGenContext parentContext, boolean isReadonly, String nodeCode,
      IGspCommonField field, UnifiedDataTypeDef udtDef,
      CompileUnitGeneratorContext compileUnitGenContext) {
    IfStatement ifStatement = ast.newIfStatement();
    String fieldName = NestedAccessorFieldGenerator.getFieldName(field);
    addNestedIfExpression(ast, ifStatement);
    addNestedIfThen(ast, parentContext, isReadonly, ifStatement, fieldName, udtDef.getUdtType(),
        nodeCode, field, compileUnitGenContext);
    addNestedIfElse(ast, ifStatement, fieldName);
    block.statements().add(ifStatement);
  }

  private static void addNestedIfExpression(AST ast, IfStatement ifStatement) {
    InfixExpression infixExpression = ast.newInfixExpression();
    MethodInvocation methodInvocation = ast.newMethodInvocation();
    methodInvocation.setName(ast.newSimpleName(InnerDataMemName));
    infixExpression.setLeftOperand(methodInvocation);
    infixExpression.setOperator(InfixExpression.Operator.NOT_EQUALS);
    infixExpression.setRightOperand(ast.newNullLiteral());
    ifStatement.setExpression(infixExpression);
  }

  private static void addNestedIfThen(AST ast, AccessorClassGenContext parentContext,
      boolean isReadonly, IfStatement ifStatement, String fieldName, String configID,
      String nodeCode, IGspCommonField field,
      CompileUnitGeneratorContext rootCompileUnitGenContext) {
    Block block = ast.newBlock();
    addNestedIfThenCreateNestedField(ast, block, parentContext, isReadonly, fieldName, nodeCode,
        configID, field, rootCompileUnitGenContext);
    addNestedIfThenSetBelongBuffer(ast, block, fieldName);
    addNestedIfThenSetPropertyName(ast, block, fieldName, field);

    ifStatement.setThenStatement(block);
  }

  private static void addNestedIfThenCreateNestedField(AST ast, Block block,
      AccessorClassGenContext parentContext, boolean isReadonly, String fieldName,
      String nodecode, String configID, IGspCommonField field,
      CompileUnitGeneratorContext rootCompileUnitGenContext) {
    Assignment assignment = ast.newAssignment();
    assignment.setLeftHandSide(ast.newSimpleName(fieldName));
    assignment.setOperator(Assignment.Operator.ASSIGN);
    ClassInstanceCreation classInstanceCreation = ast.newClassInstanceCreation();
    String enrichedAccName = isReadonly
        ? parentContext.getEntityDataGenContext().getEnrichedUdtAssoReadonlyAcsTypes()
        .get(field.getLabelID()).getFullName()
        : parentContext.getEntityDataGenContext().getEnrichedUdtAssoAcsTypes()
            .get(field.getLabelID()).getFullName();
    classInstanceCreation.setType(ast.newSimpleType(ast.newName(enrichedAccName)));
    MethodInvocation getInnerDataInvocation = ast.newMethodInvocation();
    getInnerDataInvocation.setName(ast.newSimpleName("getInnerData"));
    MethodInvocation getPropInvocation = ast.newMethodInvocation();
    getPropInvocation.setExpression(getInnerDataInvocation);
    getPropInvocation.setName(ast.newSimpleName("get" + field.getLabelID()));
    classInstanceCreation.arguments().add(getPropInvocation);
    assignment.setRightHandSide(classInstanceCreation);

    block.statements().add(ast.newExpressionStatement(assignment));
  }

  private static void addCreateAccessorExpression(AST ast, MethodInvocation createAccessor,
      String configID, IGspCommonField field) {
    MethodInvocation getAccessorCreator = ast.newMethodInvocation();
    getAccessorCreator.setName(ast.newSimpleName("getAccessorCreator"));
    addGetAccessorCreatorExpression(ast, getAccessorCreator, configID, field);

    createAccessor.setExpression(getAccessorCreator);
  }

  private static void addGetAccessorCreatorExpression(AST ast, MethodInvocation getAccessorCreator,
      String configID, IGspCommonField field) {
    MethodInvocation createManager = ast.newMethodInvocation();
    createManager.setName(ast.newSimpleName("createManager"));
    StringLiteral stringLiteral = ast.newStringLiteral();
    stringLiteral.setLiteralValue(configID);
    createManager.arguments().add(stringLiteral);
		createManager.setExpression(RepositoryGenUtil
				.createMethodInvocation(ast, ast.newSimpleName("UdtManagerUtil"), "getUdtFactory", null));
    getAccessorCreator.setExpression(createManager);
  }

  private static void addNestedIfThenSetBelongBuffer(AST ast, Block block, String fieldName) {
    MethodInvocation setBelongBuffer = ast.newMethodInvocation();
    setBelongBuffer.setName(ast.newSimpleName("setBelongBuffer"));
    ParenthesizedExpression parenthesizedExpression = ast.newParenthesizedExpression();
    CastExpression castExpression = ast.newCastExpression();
    castExpression.setExpression(ast.newSimpleName(fieldName));
    castExpression.setType(ast.newSimpleType(ast.newName(
        com.inspur.edp.cef.entity.accessor.dataType.ValueObjAccessor.class.getTypeName())));
    parenthesizedExpression.setExpression(castExpression);
    setBelongBuffer.setExpression(parenthesizedExpression);
    setBelongBuffer.arguments().add(ast.newThisExpression());
    block.statements().add(ast.newExpressionStatement(setBelongBuffer));
  }

  private static void addNestedIfThenSetPropertyName(AST ast, Block block, String fieldName,
      IGspCommonField field) {
    MethodInvocation setPropertyName = ast.newMethodInvocation();
    setPropertyName.setName(ast.newSimpleName("setPropName"));
    ParenthesizedExpression parenthesizedExpression = ast.newParenthesizedExpression();
    CastExpression castExpression = ast.newCastExpression();
    castExpression.setExpression(ast.newSimpleName(fieldName));
    castExpression.setType(ast.newSimpleType(ast.newName(
        com.inspur.edp.cef.entity.accessor.dataType.ValueObjAccessor.class.getTypeName())));
    parenthesizedExpression.setExpression(castExpression);
    setPropertyName.setExpression(parenthesizedExpression);
    StringLiteral stringLiteral = ast.newStringLiteral();
    stringLiteral.setLiteralValue(field.getLabelID());
    setPropertyName.arguments().add(stringLiteral);
    block.statements().add(ast.newExpressionStatement(setPropertyName));
  }

  private static void addNestedIfElse(AST ast, IfStatement ifStatement, String fieldName) {
    Block block = ast.newBlock();
    Assignment assignment = ast.newAssignment();
    assignment.setLeftHandSide(ast.newSimpleName(fieldName));
    assignment.setOperator(Assignment.Operator.ASSIGN);
    assignment.setRightHandSide(ast.newNullLiteral());
    block.statements().add(ast.newExpressionStatement(assignment));
    ifStatement.setElseStatement(block);
  }

  private void getDynamicInitializer(Block block, IGspCommonField field) {
    Assignment assignment = ast.newAssignment();
    assignment
        .setLeftHandSide(ast.newSimpleName(DynamicPropSetPropertyGenerator.getFieldName(field)));
    assignment.setOperator(Assignment.Operator.ASSIGN);
    MethodInvocation createAccInvocation = RepositoryGenUtil.createMethodInvocation(
        ast,
        ast.newName(DataUtils.class.getName()), "createDynPropSetAcc",
        Arrays.asList(ast.newThisExpression(),
            GeneratorUtil.getStringLiteral(ast, field.getLabelID()),
            RepositoryGenUtil.createMethodInvocation(ast, null, "getIsReadonly", null)));
    assignment.setRightHandSide(createAccInvocation);

    block.statements().add(ast.newExpressionStatement(assignment));
  }

  public static final String MethodName = "initializeNested";

  @Override
  protected String getMethodName() {
    return MethodName;
  }

  @Override
  protected TypeInfo getReturnType() {
    return new TypeInfo(void.class.getTypeName());
  }

  @Override
  protected ArrayList<Modifier.ModifierKeyword> getAccessModifiers() {
  	return RepositoryGenUtil.createModifierKeywordArray(ModifierKeyword.PUBLIC_KEYWORD);
  }

  @Override
  protected ArrayList<ParameterInfo> getParameterCollection() {
    return null;
  }
}
